#include "CUCA_engine.h"
static pthread_rwlock_t semCache = PTHREAD_RWLOCK_INITIALIZER;
static diccionario_t diccionario = { NULL, NULL };
static uint8_t algoritmoEleccion;
static uint8_t algoritmoReemplazo;
static size_t CacheSize;
static size_t ChunkSize;

/*FUNCIONES DUMMY*/
ENGINE_ERROR_CODE CUCA_get_stats(ENGINE_HANDLE* handle, const void* cookie, const char* stat_key, int nkey, ADD_STAT add_stat) {
	return ENGINE_SUCCESS;
}

void CUCA_reset_stats(ENGINE_HANDLE* handle, const void *cookie) {

}

ENGINE_ERROR_CODE CUCA_unknown_command(ENGINE_HANDLE* handle, const void* cookie, protocol_binary_request_header *request, ADD_RESPONSE response) {
	return ENGINE_ENOTSUP;
}

void CUCA_item_set_cas(ENGINE_HANDLE *handle, const void *cookie, item* item, uint64_t val) {

}

// FUNCIONES CUCA
MEMCACHED_PUBLIC_API ENGINE_ERROR_CODE create_instance(uint64_t interface, GET_SERVER_API get_server_api, ENGINE_HANDLE **handle){



	CUCA_engine_t *engine = calloc(1, sizeof(CUCA_engine_t));
	if (engine == NULL) {
		return ENGINE_ENOMEM;
	}
	//cargamos nuestras funciones a la estructura
	engine->engine.initialize = CUCA_initialize;
	engine->engine.destroy = CUCA_destroy;
	engine->engine.get_info = CUCA_get_info;
	engine->engine.allocate = CUCA_allocate;
	engine->engine.release = CUCA_item_release;
	engine->engine.get_stats = CUCA_get_stats;
	engine->engine.reset_stats = CUCA_reset_stats;
	engine->engine.store = CUCA_store;
	engine->engine.unknown_command = CUCA_unknown_command;
	engine->engine.item_set_cas = CUCA_item_set_cas;
	engine->engine.get_item_info = CUCA_get_item_info;
	engine->engine.remove = CUCA_remove;
	engine->engine.get = CUCA_get;
	engine->engine.store = CUCA_store;
	engine->engine.flush = CUCA_flush;


	*handle = (ENGINE_HANDLE*) engine;


	return ENGINE_SUCCESS;
}

ENGINE_ERROR_CODE CUCA_initialize(ENGINE_HANDLE* handle, const char* config_str){

		//CUCA_engine_t *engine = (CUCA_engine_t*)handle;
		//TODO modificar los parametros de leerArchivoConfiguracion
		archConfig_t arch;
		leerArchivoConfiguracion(&arch, 1);

		diccionario.listaCache = inicializarCache(arch.tamanio);

		if(strcmp(arch.algoritmoEleccion, "NextFit") == 0){
			algoritmoEleccion = ALGORITMO_NEXT_FIT;
		}else{
			algoritmoEleccion = ALGORITMO_WORST_FIT;
		}
		if(strcmp(arch.algoritmoReemplazo, "FIFO") == 0){
			algoritmoReemplazo = ALGORITMO_FIFO;
		}else{
			algoritmoReemplazo = ALGORITMO_LRU;
		}

		if (config_str != NULL) {
			struct config_item items[] = {
				 { .key = "cache_size",
				   .datatype = DT_SIZE,
				   .value.dt_size = &CacheSize},
				 { .key = "chunk_size",
				   .datatype = DT_SIZE,
				   .value.dt_size = &ChunkSize },
				 { .key = NULL}
			};

			parse_config(config_str, items, NULL);
		}

	signal(SIGUSR1, DumpearCache);

	return ENGINE_SUCCESS;
}

void CUCA_destroy(ENGINE_HANDLE* handle, const bool force){
	free(handle);
}

const engine_info* CUCA_get_info(ENGINE_HANDLE* handle) {
	static engine_info info = {
	          .description = "CUCA Engine definitive version ",
	          .num_features = 0,
	          .features = {
	               [0].feature = ENGINE_FEATURE_LRU,
	               [0].description = "No hay soporte de LRU",
	           }
	};

	return &info;
}


ENGINE_ERROR_CODE CUCA_allocate(ENGINE_HANDLE *handler, const void* cookie, item **item, const void* key, const size_t nkey, const size_t nbytes, const int flags, const rel_time_t exptime){

	CUCA_item_t *it = malloc( sizeof(CUCA_item_t) );

	if (it == NULL) {
		return ENGINE_ENOMEM;
	}

	//it->flags = flags;
	//it->exptime = exptime;
	it->key_length = nkey;
	it->data_length = nbytes;
	it->key = malloc(nkey);
	it->data = malloc(nbytes);
	//it->stored = false;

	memcpy(it->key, key, nkey);
	*item = it;

	return ENGINE_SUCCESS;
}


void CUCA_item_release(ENGINE_HANDLE *handler, const void *cookie, item* item){
	CUCA_item_t *it = (CUCA_item_t*)item;
	//if( !it->stored ){
		free(it->key);
		free(it->data);
		free(it);
	//}
}

bool CUCA_get_item_info(ENGINE_HANDLE *handler, const void *cookie, const item* item, item_info *item_info){
	CUCA_item_t *it = (CUCA_item_t*)item;

	if (item_info->nvalue < 1) {
	  return false;
	}

	item_info->cas = 0;
	item_info->clsid = 0;
	//item_info->exptime = it->exptime;
	//item_info->flags = it->flags;
	item_info->key = it->key;
	item_info->nkey = it->key_length;
	item_info->nbytes = it->data_length;
	item_info->nvalue = 1;
	item_info->value[0].iov_base = it->data;
	item_info->value[0].iov_len = it->data_length;

	return true;
}

ENGINE_ERROR_CODE CUCA_get(ENGINE_HANDLE *handle, const void* cookie, item** item, const void* key, const int32_t key_length, uint16_t vbucket){
	pthread_rwlock_rdlock(&semCache);
	//busco un valor por la key, si lo encuentro lo devuelvo.
	//caso contrario devuelvo codigo de error
	memcached_return_t retorno;
	memCache_t* aux = diccionario.listaCache;
	uint8_t encontrado = 0;
	while (!encontrado && aux != NULL) {
		if (strcmp((const char*)key, aux->key) == 0) {
			encontrado = 1;
		} else {
			aux = aux->sig;
		}
	}
	char* info = NULL;
	if (encontrado) {
		if (algoritmoReemplazo == ALGORITMO_LRU) {
			aux->tiempo = time(NULL);
		}
		info = leerCache(diccionario.cache, aux->posInicio * CHUNKSIZE, aux->tamanio * CHUNKSIZE);
		retorno = MEMCACHED_SUCCESS;
	} else {
		info = NULL;
		retorno = MEMCACHED_DATA_DOES_NOT_EXIST;
	}
	CUCA_item_t *CUCA_item = malloc(sizeof(CUCA_item_t));
	CUCA_item->key = malloc(key_length + 1);
	memset(CUCA_item->key, '\0', key_length+1);
	memcpy(CUCA_item->key, key, key_length);
	CUCA_item->key_length = key_length;
	CUCA_item->data = malloc(aux->tamanio);
	memset(CUCA_item->data, '\0', aux->tamanio);
	memcpy(CUCA_item->data, info, aux->tamanio);
	CUCA_item->data_length = aux->tamanio;

	*item = (void*) CUCA_item;
	pthread_rwlock_unlock(&semCache);
	return retorno;
}

ENGINE_ERROR_CODE CUCA_store(ENGINE_HANDLE *handle, const void *cookie, item* item, uint64_t *cas, ENGINE_STORE_OPERATION operation, uint16_t vbucket) {
	/*que pasa si se intenta escribir un dato que ya se encuentra en la cache?????*/
	pthread_rwlock_wrlock(&semCache);
	CUCA_item_t *CUCA_item =(CUCA_item_t*) item;
	//almaceno un valor y lo asocio a su key
	div_t division = div(CUCA_item->data_length, CHUNKSIZE);
	uint32_t cantChunks = division.quot;
	if (division.rem > 0) {
		cantChunks++;
	}
	memcached_return_t retorno;
	if (algoritmoEleccion == ALGORITMO_WORST_FIT) {
		memCache_t* espacioASetear = worstFit(diccionario.listaCache, cantChunks);
		if (espacioASetear == NULL) {
			compactarCache(diccionario.cache, &(diccionario.listaCache));
			espacioASetear = worstFit(diccionario.listaCache, cantChunks);
			if (espacioASetear == NULL) {
				/*hay que eliminar nodos para hacer espacio*/
				retorno = MEMCACHED_FAILURE;
			} else {
				cargarCache(espacioASetear, cantChunks, CUCA_item->key, diccionario, CUCA_item->data);
				retorno = MEMCACHED_SUCCESS;
			}
		} else {
			cargarCache(espacioASetear, cantChunks, CUCA_item->key, diccionario, CUCA_item->data);
			retorno = MEMCACHED_SUCCESS;
		}
	} else if (algoritmoEleccion == ALGORITMO_NEXT_FIT) {
		memCache_t* espacioASetear = NextFit(diccionario.listaCache, cantChunks);
		if (espacioASetear == NULL) {
			compactarCache(diccionario.cache, &(diccionario.listaCache));
			espacioASetear = NextFit(diccionario.listaCache, cantChunks);
			if (espacioASetear == NULL) {
				/*hay que eliminar nodos para hacer espacio*/
				retorno = MEMCACHED_FAILURE;
			} else {
				cargarCache(espacioASetear, cantChunks, CUCA_item->key, diccionario, CUCA_item->data);
				retorno = MEMCACHED_SUCCESS;
			}
		} else {
			cargarCache(espacioASetear, cantChunks, CUCA_item->key, diccionario, CUCA_item->data);
			retorno = MEMCACHED_SUCCESS;
		}
	}
	pthread_rwlock_unlock(&semCache);
	return retorno;
}

ENGINE_ERROR_CODE CUCA_remove(ENGINE_HANDLE* handle, const void* cookie, const void* key, const size_t key_length, uint64_t cas, uint16_t vbucket){
	pthread_rwlock_wrlock(&semCache);
	//busco un valor y lo elimino, sacando su key también
	memcached_return_t retorno;
	memCache_t* victimaAnt = NULL;
	memCache_t* victima = diccionario.listaCache;
	while (victima != NULL && strcmp(victima->key, (const char*) key) != 0) {
		victimaAnt = victima;
		victima = victima->sig;
	}
	if (strcmp(victima->key, (const char*) key) == 0) {
		if (victima == diccionario.listaCache) {
			victima->estado = 'L';

			if (victima->sig != NULL && victima->sig->estado == 'L') {
				unirNodosLibres(victima, &(victima->sig));
				free(victima->key);
				victima->key = NULL;
			}
		} else {
			victima->estado = 'L';
			if (victima->sig != NULL && victima->sig->estado == 'L') {
				unirNodosLibres(victima, &(victima->sig));
				free(victima->key);
				victima->key = NULL;
			}
			if (victimaAnt->estado == 'L') {
				unirNodosLibres(victimaAnt, &(victima));
			}
		}
		retorno = MEMCACHED_SUCCESS;
	}else{
		retorno = MEMCACHED_DATA_DOES_NOT_EXIST;
	}
	pthread_rwlock_unlock(&semCache);
	return retorno;
}

ENGINE_ERROR_CODE CUCA_flush(ENGINE_HANDLE* handle, const void* cookie, time_t expiration){
	pthread_rwlock_wrlock(&semCache);
	/* recorro toda la cache, haciendo remove
	 * sobre cada valor encontrado
	 */
	memCache_t* aux;
	uint32_t tamanioCache = 0;
	while (diccionario.listaCache != NULL) {
		aux = diccionario.listaCache;
		tamanioCache += aux->tamanio;
		diccionario.listaCache = diccionario.listaCache->sig;
		if (aux->key != NULL) {
			free(aux->key);
		}
		free(aux);
	}
	diccionario.listaCache = inicializarCache(tamanioCache);
	pthread_rwlock_unlock(&semCache);
	return MEMCACHED_SUCCESS;
}

void DumpearCache(int32_t signal){
	pthread_rwlock_rdlock(&semCache);
	uint8_t i = 0;
	memCache_t *aux = diccionario.listaCache;
	while(aux != NULL){
		printf("particion %d: .", i);
		//convertir a hexa
		if(aux->estado == 'O'){
			printf("Ocupada.");
		} else {
			printf("Libre.");
		}
		printf("Size: %d bytes.", aux->tamanio*CHUNKSIZE);
		if(aux->estado == 'O'){
			printf("Key: \"%s\" \n", aux->key);
		}
		aux = aux->sig;
	}
	pthread_rwlock_unlock(&semCache);
	return;
}
